#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "flash_eeprom.h"

#define PRINTF(...) //printf(__VA_ARGS__)

int port_flash_init()
{
    return 0;
}

int port_flash_erase_sector(uint32_t addr)
{
    PRINTF("-----port_flash_erase_sector: addr = %d[%08X]\n", addr, addr);
	memset((void*)addr, 0xff, FLASH_EEPROM_SECTOR_SIZE);
    return 0;
}

int port_flash_erase_block(uint32_t addr)
{
    PRINTF("-----port_flash_erase_block: addr = %d[%08X]\n", addr, addr);
	memset((void*)addr, 0xff, FLASH_EEPROM_BLOCK_SIZE);
    return 0;
}

int port_flash_write_data(uint32_t addr, uint8_t *data, uint32_t len)
{
    PRINTF("-----port_flash_write_data: addr = %d[%08X], len = %d\n", addr, addr, len);
    memcpy((uint8_t*)addr, data, len);
    return len;
}

int port_flash_read_data(uint32_t addr, uint8_t *buff, uint32_t len)
{
    memcpy(buff, (uint8_t*)addr, len);
    PRINTF("-----port_flash_read_data: addr = %d[%08X], len = %d\n", addr, addr, len);
    return len;
}

uint32_t flash_eeprom_makeid(const char *name) 
{
     return (name[0] << 24) | (name[1] << 16) | (name[2] << 8) | (name[3]); 
}

typedef struct {
    uint32_t id;
    uint32_t addr;
    uint32_t len;
}flash_eeprom_data_t;

static uint32_t s_base_addr;
static uint32_t s_data_addr;
static uint32_t s_data_info_addr;
static uint32_t s_remain_size;
static uint8_t s_block_num;

static flash_eeprom_data_t s_data_infos[FLAsh_EEPROM_MAX_VARIABLES];

static inline int _flash_write_data_info(uint32_t addr, flash_eeprom_data_t *info)
{
    return port_flash_write_data(addr, (uint8_t*)info, sizeof(flash_eeprom_data_t));
}

static inline int _flash_read_data_info(uint32_t addr, flash_eeprom_data_t *info)
{
    return port_flash_read_data(addr, (uint8_t*)info, sizeof(flash_eeprom_data_t));
}
    
static inline int _flash_write_data(flash_eeprom_data_t *info, uint8_t *data)
{
    return port_flash_write_data(info->addr, data, info->len);
}

static inline int _flash_read_data(flash_eeprom_data_t *info, uint8_t *buf)
{
    return port_flash_read_data(info->addr, buf, info->len);
}

static inline uint32_t get_flash_base_addr() 
{
    return s_base_addr;
}
static inline uint32_t get_flash_end_addr()
{
    return s_base_addr + s_block_num * FLASH_EEPROM_BLOCK_SIZE;
}
static inline uint32_t get_flash_data_info_base_addr()
{
    /* resever magic number in high side*/
    return get_flash_end_addr() -
           2 * sizeof(flash_eeprom_data_t);
}

static int get_data_info_by_id(uint32_t id, flash_eeprom_data_t *data_info)
{
    uint8_t flag = 0;
    for (int i = 0; i < FLAsh_EEPROM_MAX_VARIABLES; i++) {
        if (s_data_infos[i].id == id) {
            data_info->id = s_data_infos[i].id;
            data_info->addr = s_data_infos[i].addr;
            data_info->len = s_data_infos[i].len;
            flag = 1;
            break;
        };
    }
    return flag ? 0 : FLASH_EEPROM_ERROR_BAD_ID;
}
static int set_data_info(flash_eeprom_data_t *data_info)
{
    int i = 0;
    while (i < FLAsh_EEPROM_MAX_VARIABLES) {
        if (s_data_infos[i].id == FLASH_EEPROM_INVALID_ID || 
            s_data_infos[i].id == data_info->id) {
            break;
        }
        i++;
    }
    if (i == FLAsh_EEPROM_MAX_VARIABLES) {
        return FLASH_EEPROM_ERROR_TOO_MUSH_VAR;
    }
    s_data_infos[i].id = data_info->id;
    s_data_infos[i].addr = data_info->addr;
    s_data_infos[i].len = data_info->len;
    return 0;
}

int flash_eeprom_init(uint32_t base_addr, uint8_t n_blocks)
{
    s_block_num = n_blocks;
    s_base_addr = base_addr;
    #if 1
    PRINTF("-----flash blocks start: %d[%08X]\n",base_addr, base_addr);
    for (int addr = 0; addr < n_blocks * FLASH_EEPROM_BLOCK_SIZE; addr += FLASH_EEPROM_SECTOR_SIZE) {
        PRINTF("-----    sector%02d: %d[%08X]\n", addr/FLASH_EEPROM_SECTOR_SIZE, s_base_addr+addr, s_base_addr+addr);
    }
    PRINTF("-----flash blocks end: %d[%08X]\n", get_flash_end_addr(), get_flash_end_addr());
    #endif
    s_data_addr = get_flash_base_addr();
    s_data_info_addr = get_flash_data_info_base_addr();
    s_remain_size = s_data_info_addr - s_data_addr;
    // set s_data_infos to 0xff to same with flash reset value
    memset(s_data_infos, FLASH_EEPROM_RESET_VAL, sizeof(s_data_infos));
    
    flash_eeprom_data_t info;
    _flash_read_data_info(s_data_info_addr + sizeof(flash_eeprom_data_t), &info);
    if (info.id != FLASh_EEPROM_MAGIC_ID1 && info.addr != FLASh_EEPROM_MAGIC_ID2) {
        // flash has no valid data
        for (uint8_t n = 0; n < n_blocks; n++) {
            port_flash_erase_block(base_addr + n * FLASH_EEPROM_BLOCK_SIZE);
        }
        info.id = FLASh_EEPROM_MAGIC_ID1;
        info.addr = FLASh_EEPROM_MAGIC_ID2;
        _flash_write_data_info(get_flash_end_addr() - sizeof(flash_eeprom_data_t), &info);
        //PRINTF("-----remain free flash size = %d\n", s_remain_size);
        return 0;
    }
    // read flash data info
    while (1) {
        
        info.addr = s_data_info_addr;
        info.len = sizeof(flash_eeprom_data_t);
        _flash_read_data(&info, (uint8_t*)&info);
        if (info.id == FLASH_EEPROM_INVALID_ID) {
            break;
        }
        
        set_data_info(&info);
        
        s_remain_size -= (info.len + sizeof(flash_eeprom_data_t));
        if (s_remain_size <= 2 * sizeof(flash_eeprom_data_t)) {
            PRINTF("flash has no enough memory\n");
            return FLASH_EEPROM_ERROR_NO_MEM;
        }
        s_data_addr += info.len;
        s_data_info_addr -= sizeof(flash_eeprom_data_t);
        // TODO: check if overflow
    }
    PRINTF("-----remain free flash size = %d\n", s_remain_size);
    return 0;
}

// sort data info list by address
#define SWITCH(a, b) {uint32_t tmp = a; a = b; b = tmp;}
static int sort_data_info_list()
{
    int num = 0;
    while (num < FLAsh_EEPROM_MAX_VARIABLES) {
        if (s_data_infos[num].id == FLASH_EEPROM_INVALID_ID)
            break;
        num++;
    }
    if (num  == 0)
        return num;
    
    uint32_t addr = s_data_infos[0].addr;
    
    for (int i = 0; i < num; i++) {
        flash_eeprom_data_t info;
        info.id = s_data_infos[i].id;
        info.addr = s_data_infos[i].addr;
        info.len = s_data_infos[i].len;
        for (int j = i + 1; j < num; j++) {
            if (s_data_infos[j].addr < info.addr) {
                SWITCH(s_data_infos[j].id, info.id);
                SWITCH(s_data_infos[j].addr, info.addr);
                SWITCH(s_data_infos[j].len, info.len);
            }
        }
        s_data_infos[i].id = info.id;
        s_data_infos[i].addr = info.addr;
        s_data_infos[i].len = info.len;
    }
    return num;
}

static int flash_eeprom_write_(uint32_t id, uint8_t *data, uint32_t len)
{
    flash_eeprom_data_t info;
    int ret = 0;
    if (len + sizeof(flash_eeprom_data_t) > s_remain_size) {
        return FLASH_EEPROM_ERROR_NO_MEM;
    }
    info.id = id;
    info.addr = s_data_addr;
    info.len = len;
    ret = _flash_write_data(&info, data);
    // increase data address even if write failed
    s_data_addr += len;
    s_remain_size -= len;
    if (ret < 0)
        return ret;
    ret = _flash_write_data_info(s_data_info_addr, &info);
    // increase data info address even if write failed
    s_data_info_addr -= sizeof(flash_eeprom_data_t);
    s_remain_size -= sizeof(flash_eeprom_data_t);
    
    if (ret < 0)
        return ret;
    ret = set_data_info(&info);
    PRINTF("-----remain free flash size = %d\n", s_remain_size);
    return ret;
}

static void erase_infos_sector()
{
    uint32_t addr = s_data_info_addr - (s_data_info_addr % FLASH_EEPROM_SECTOR_SIZE);
    while (addr < get_flash_end_addr()) {
        port_flash_erase_sector(addr);
        addr += FLASH_EEPROM_SECTOR_SIZE;
    } 
}

int flash_eeprom_flush(uint8_t flag)
{
    if (flag == 0 && s_remain_size > FLASH_EEPROM_SECTOR_SIZE) {
        return 0;
    }
    //PRINTF("flash_eeprom_flush: %d\n", flag);
    
    // reset data info sector
    erase_infos_sector();
    flash_eeprom_data_t info;
    info.id = FLASh_EEPROM_MAGIC_ID1;
    info.addr = FLASh_EEPROM_MAGIC_ID2;
    _flash_write_data_info(get_flash_end_addr() - sizeof(flash_eeprom_data_t), &info);
    
    s_data_addr = get_flash_base_addr();
    s_data_info_addr = get_flash_data_info_base_addr();
    s_remain_size = s_data_info_addr - s_data_addr;
    
    // remove data
    int index = 0;
    int len = 0;
    uint8_t buff[2 * FLASH_EEPROM_SECTOR_SIZE];
    int num = sort_data_info_list();
    uint32_t earse_addr2 = s_data_addr;
    for (int i = 0; i < num;) {
        uint32_t earse_addr1 = earse_addr2;
        // read flash data to buffer
        while (1) {
            if (s_data_infos[i].id == FLASH_EEPROM_INVALID_ID)
                break;
            _flash_read_data(&s_data_infos[i], buff + len);
            len += s_data_infos[i].len;
            earse_addr2 = s_data_infos[i].addr + s_data_infos[i].len;
            i++;
            if (len >= FLASH_EEPROM_SECTOR_SIZE)
                break;
        }
        PRINTF("-----read data to buffer: len = %d\n", len);
        // earse no valid sectors
        do {
            port_flash_erase_sector(earse_addr1);
            earse_addr1 += FLASH_EEPROM_SECTOR_SIZE;
        } while (earse_addr1 + FLASH_EEPROM_SECTOR_SIZE <= earse_addr2);
        earse_addr2 = earse_addr1;
        // write back buffer data
        uint8_t *pdata = buff;
        while (index < i) {
            if (s_data_infos[index].id == FLASH_EEPROM_INVALID_ID || 
                s_data_addr + s_data_infos[index].len >= earse_addr2) {
                break;
            }
            flash_eeprom_write_(s_data_infos[index].id, pdata, s_data_infos[index].len);
            pdata += s_data_infos[index].len;
            index++;
        }
        PRINTF("-----write data back: len = %d\n", pdata - buff);
        // has data not write back
        len -= (pdata - buff);
        if (len) {
            memcpy(buff, pdata, len);
        }
    }
    // earse remain flash sectors
    while (earse_addr2 < s_data_info_addr - FLASH_EEPROM_SECTOR_SIZE) {
        port_flash_erase_sector(earse_addr2);
        earse_addr2 += FLASH_EEPROM_SECTOR_SIZE;
    }
    // check if buff has data
    uint8_t *pdata = buff;
    while (len) {
        flash_eeprom_write_(s_data_infos[index].id, pdata, s_data_infos[index].len);
        len -= s_data_infos[index].len;
        pdata += len;
        index++;
    }
    return 0;
}

int flash_eeprom_write(uint32_t id, uint8_t *data, uint32_t len)
{
    flash_eeprom_data_t info;
    int ret = 0;
    flash_eeprom_flush(0);
    if (len + sizeof(flash_eeprom_data_t) > s_remain_size) {
        flash_eeprom_flush(1);
        if (len + sizeof(flash_eeprom_data_t) > s_remain_size)
            return FLASH_EEPROM_ERROR_NO_MEM;
    }
    
    return flash_eeprom_write_(id, data, len);
}

int flash_eeprom_read(uint32_t id, uint8_t *buff, uint32_t len)
{
    flash_eeprom_data_t info;
    int ret = 0;
    ret = get_data_info_by_id(id, &info);
    if (ret < 0)
        return ret;
    if (info.len > len)
        info.len = len;
    return _flash_read_data(&info, buff);
}

int flash_eeprom_erase_all()
{
    for (uint8_t n = 0; n < s_block_num; n++) {
        port_flash_erase_block(s_base_addr + n * FLASH_EEPROM_BLOCK_SIZE);
    } 
    return 0;
}
